#include "ogrebindingPCH.h"
#include "vector3binding.h"

namespace lua_ogre {

const char *Vector3ClassName = "Vector3";

int Vec3ToString(lua_State *ls)
{
	const Ogre::Vector3 &vec = *(Ogre::Vector3*)lua_touserdata(ls, 1);
	lua_pushfstring(ls, "Vector3(%f, %f, %f)", 
		vec.x, vec.y, vec.z);
	return 1;
}

int Vec3Alloc(lua_State *ls)
{
	lua_remove(ls, 1);

	Ogre::Vector3 vec(.0f);
	int arg_count = lua_gettop(ls);
	if (arg_count == 1)
	{
		if (lua_isnumber(ls, 1))
		{
			vec.x = vec.y = vec.z = (float)lua_tonumber(ls, 1);
		}
		else if (luaL_checkudata(ls, 1, Vector3ClassName))
		{
			vec = *(Ogre::Vector3*)lua_touserdata(ls, 1);
		}
		else
		{
			LogLuaError << "Invalid parameter!";
			return 0;
		}
	}
	else if (arg_count == 3)
	{
		for (int i = 0; i < arg_count; ++i)
		{
			vec.ptr()[i] = (float)lua_tonumber(ls, i + 1);
		}
	}
	else if (arg_count != 0)
	{
		LogLuaError << "invalid parameters!";
		return 0;
	}

	Ogre::Vector3 *ptr_vec = (Ogre::Vector3*)lua_newuserdata(ls, sizeof(Ogre::Vector3));
	if (NULL == ptr_vec)
	{
		LogLuaError << "failed to create Vector3";
		return 0;
	}

	*ptr_vec = vec;
	luaL_getmetatable(ls, Vector3ClassName);
	lua_setmetatable(ls, -2);
	return 1;
}

int LuaCreateVector3(const Ogre::Vector3 &value, lua_State *ls)
{
	Ogre::Vector3 *vec_ptr = (Ogre::Vector3*)lua_newuserdata(ls, sizeof(Ogre::Vector3));
	*vec_ptr = value;
	luaL_getmetatable(ls, Vector3ClassName);
	lua_setmetatable(ls, -2);
	return 1;
}

static int SetX(Ogre::Vector3 *self, lua_State *ls)
{
	if (!lua_isnumber(ls, 1))
	{
		LogLuaError << "invalid value type for Vector3.x";
		return 0;
	}
	self->x = (float)lua_tonumber(ls, 1);
	return 0;
}

static int GetX(Ogre::Vector3 *self, lua_State *ls)
{
	lua_pushnumber(ls, self->x);
	return 1;
}

static int SetY(Ogre::Vector3 *self, lua_State *ls)
{
	if (!lua_isnumber(ls, 1))
	{
		LogLuaError << "invalid value type for Vector3.y";
		return 0;
	}
	self->y = (float)lua_tonumber(ls, 1);
	return 0;
}

static int GetY(Ogre::Vector3 *self, lua_State *ls)
{
	lua_pushnumber(ls, self->y);
	return 1;
}

static int SetZ(Ogre::Vector3 *self, lua_State *ls)
{
	if (!lua_isnumber(ls, 1))
	{
		LogLuaError << "invalid value type for Vector3.z";
		return 0;
	}
	self->z = (float)lua_tonumber(ls, 1);
	return 0;
}

static int GetZ(Ogre::Vector3 *self, lua_State *ls)
{
	lua_pushnumber(ls, self->z);
	return 1;
}

static int Add(lua_State *ls)
{
	Ogre::Vector3 *self = (Ogre::Vector3*)lua_touserdata(ls, 1);
	if (!luaL_checkudata(ls, 2, Vector3ClassName))
	{
		LogLuaError << "Invalid rhs value for Vector3 add";
		return 0;
	}

	Ogre::Vector3 *ptr_vec = (Ogre::Vector3*)lua_newuserdata(ls, sizeof(Ogre::Vector3));
	const Ogre::Vector3 *other = (Ogre::Vector3*)lua_touserdata(ls, 2);
	(*ptr_vec) = (*other) + (*self);
	luaL_getmetatable(ls, Vector3ClassName);
	lua_setmetatable(ls, -2);
	return 1;
}

static int Sub(lua_State *ls)
{
	Ogre::Vector3 *self = (Ogre::Vector3*)lua_touserdata(ls, 1);
	if (!luaL_checkudata(ls, 2, Vector3ClassName))
	{
		LogLuaError << "Invalid rhs value for Vector3 sub";
		return 0;
	}

	Ogre::Vector3 *ptr_vec = (Ogre::Vector3*)lua_newuserdata(ls, sizeof(Ogre::Vector3));
	const Ogre::Vector3 *other = (Ogre::Vector3*)lua_touserdata(ls, 2);
	(*ptr_vec) = (*self) - (*other);
	luaL_getmetatable(ls, Vector3ClassName);
	lua_setmetatable(ls, -2);
	return 1;
}

static int Mul(lua_State *ls)
{
	Ogre::Vector3 *self = (Ogre::Vector3*)lua_touserdata(ls, 1);
	if (!lua_isnumber(ls, 2))
	{
		LogLuaError << "Invalid rhs value for Vector3 mul";
		return 0;
	}

	Ogre::Vector3 *ptr_vec = (Ogre::Vector3*)lua_newuserdata(ls, sizeof(Ogre::Vector3));
	(*ptr_vec) = (*self) * (Ogre::Real)lua_tonumber(ls, 2);
	luaL_getmetatable(ls, Vector3ClassName);
	lua_setmetatable(ls, -2);
	return 1;
}

static int SetZero(Ogre::Vector3 *self, lua_State *ls)
{
	self->x = self->y = self->z = .0f;
	return 0;
}

static int Normalize(Ogre::Vector3 *self, lua_State *ls)
{
	self->normalise();
	if (lua_isnumber(ls, 1))
	{
		*self *= (Ogre::Real)lua_tonumber(ls, 1);
	}
	return 0;
}

static int IsZero(Ogre::Vector3 *self, lua_State *ls)
{
	if (self->positionEquals(Ogre::Vector3::ZERO, 0.0001f))
	{
		lua_pushboolean(ls, 1);
	}
	else
	{
		lua_pushboolean(ls, 0);
	}
	return 1;
}

LuaMethod<Ogre::Vector3> g_vec3_methods_and_props[] = 
{
	{"x", 0, GetX, SetX},
	{"y", 0, GetY, SetY},
	{"z", 0, GetZ, SetZ},
	{"Zero", SetZero, 0, 0},
	{"Normalize", Normalize, 0, 0},
	{"IsZero", IsZero, 0, 0},
	{0, 0, 0, 0}
};

bool BindVector3(lua_State *ls)
{
	luaL_newmetatable(ls, Vector3ClassName);
	int metatable = lua_gettop(ls);

	lua_pushvalue(ls, metatable);
	lua_setmetatable(ls, metatable);

	luaL_Reg empty_reg = {NULL, NULL};
	luaL_register(ls, "ov", &empty_reg);
	int module_table = lua_gettop(ls);
	lua_pushstring(ls, Vector3ClassName);
	lua_pushvalue(ls, metatable);
	lua_rawset(ls, module_table);
	lua_pop(ls, 1);

	lua_pushstring(ls, "__tostring");
	lua_pushcfunction(ls, Vec3ToString);
	lua_rawset(ls, metatable);

	// it doesn't need __gc
	
	lua_pushstring(ls, "__call");
	lua_pushcfunction(ls, Vec3Alloc);
	lua_rawset(ls, metatable);
	
	lua_pushstring(ls, "__index");
	lua_pushcfunction(ls, LuaClassIndex<Ogre::Vector3>);
	lua_rawset(ls, metatable);

	lua_pushstring(ls, "__add");
	lua_pushcfunction(ls, Add);
	lua_rawset(ls, metatable);
	lua_pushstring(ls, "__sub");
	lua_pushcfunction(ls, Sub);
	lua_rawset(ls, metatable);
	lua_pushstring(ls, "__mul");
	lua_pushcfunction(ls, Mul);
	lua_rawset(ls, metatable);

	lua_pushstring(ls, "__newindex");
	lua_pushcfunction(ls, LuaClassSetter<Ogre::Vector3>);
	lua_rawset(ls, metatable);

	LuaMethod<Ogre::Vector3> *m = g_vec3_methods_and_props;
	while (m->name != NULL)
	{
		lua_pushstring(ls, m->name);
		lua_pushlightuserdata(ls, m);
		lua_rawset(ls, metatable);
		++m;
	}

	lua_pop(ls, 1);
	return true;
}

}